// PlyViewer.cpp : Defines the entry point for the application.
//

#include <stdlib.h>
#include <iostream>

#include "Renderer.h"
#include "Application.h"
#include "FileOps.h"
#include "math.h"

#include "PlyViewer.h"

using namespace std;

// renderer initialized in main file
extern Renderer *g_renderer;

const float			FOG_DISTANCE = 50;

const float			BG_COLOR[] = {0, 0, 0, 1};


//
// @brief	Main entry
//
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	AppConfig config;
	config.appName = "PLY Viewer";
	config.cmdShow = nCmdShow;
	config.height = 900;
	config.width = 1200;
	config.hInstance = hInstance;
	
	PlyViewer viewer;
	config.scene = &viewer;

	return Application::GetInstance()->Process( config );
}


PlyViewer::PlyViewer()
{
}

PlyViewer::~PlyViewer()
{
}


bool PlyViewer::HandleMessage( UINT msg, WPARAM wParam, LPARAM lParam )
{
	bool handled = true;

	switch( msg )
	{
	case WM_KEYDOWN:
		switch( wParam )
		{	
			case VK_SPACE:
				m_camera.Reset();
				break;
			case 'W':
				m_mesh.m_render_mode = (m_mesh.m_render_mode == Mesh::RENDER_MODE_DEFAULT) ? Mesh::RENDER_MODE_WIREFRAME : Mesh::RENDER_MODE_DEFAULT;
				break;
			case 'M':
				if ( m_mesh.m_render_method == Mesh::RENDER_METHOD_IMMEDIATE )
					m_mesh.m_render_method = Mesh::RENDER_METHOD_VERTEX_ARRAYS;
				else if ( m_mesh.m_render_method == Mesh::RENDER_METHOD_VERTEX_ARRAYS )
					m_mesh.m_render_method = Mesh::RENDER_METHOD_VERTEX_BUFFER_OBJECT;
				else
					m_mesh.m_render_method = Mesh::RENDER_METHOD_IMMEDIATE;
				break;
			case VK_UP:
				m_max_faces_to_render += 10000;
				if ( m_max_faces_to_render > m_mesh.GetNumFaces() )
					m_max_faces_to_render = m_mesh.GetNumFaces();
				break;
			case VK_DOWN:
				if ( m_max_faces_to_render < 10000 )
					m_max_faces_to_render = 0;
				else if ( m_max_faces_to_render > m_mesh.GetNumFaces() )
					m_max_faces_to_render = m_mesh.GetNumFaces();
				else
					m_max_faces_to_render -= 10000;
				break;
			default:
				handled = false;
				break;
		}
	default:
		handled = false;
		break;
	};

	if ( !handled )
		return Scene::HandleMessage( msg, wParam, lParam );

	return true;
}

bool PlyViewer::Init()
{
	Scene::Init();


	int width = m_screen_width;
	int height = m_screen_height;

	// Setup render specific stuff
	{
		// Setup viewport and projection matrix
		glViewport( 0, 0, width, height );

		glMatrixMode( GL_PROJECTION );
		glLoadIdentity();
		m_camera.glPerspective( width, height );

		// Other OpenGL flags
		glEnable(GL_DEPTH_TEST);				// Depthtest
		glDepthFunc(GL_LESS);
		//glEnable(GL_CULL_FACE);					// Cull face
		//glEnable(GL_BLEND);						// Blending
		//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glShadeModel(GL_SMOOTH);				// Shading
	}

	// FOG
	{
		glEnable(GL_FOG);
		glFogf( GL_FOG_END, FOG_DISTANCE );
		glFogi( GL_FOG_MODE, GL_LINEAR);
		glFogfv( GL_FOG_COLOR, BG_COLOR );
	}

	// Setup lighting
	{
		glEnable(GL_LIGHTING);

		//float lightPos[]		= { 0.f, 0.f, 5.f, 1.f };
		float lightPos[]		= { 0.f, 0.15f, 0.1f, 1.f };
		float lightAmbient[]	= { 0.2f, 0.2f, 0.2f, 1.f };
		float lightDiffuse[]	= { 0.7f, 0.7f, 0.7f, 1.f };
		float lightSpecular[]	= { 1, 1, 1, 1 };
		
		glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
		glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpecular);
		glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
		glEnable(GL_LIGHT0);


		//float lightPos2[]		= { -10.5f, 0.f, 0.f, 0.0f };
		//float lightAmbient2[]	= { 0.1f, 0.1f, 0.1f, 0.1f };
		//float lightDiffuse2[]	= { 0.1f, 0.1f, 0.1f, 0.1f };
		//glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmbient2);
		//glLightfv(GL_LIGHT1, GL_DIFFUSE, lightDiffuse2);
		//glLightfv(GL_LIGHT1, GL_POSITION, lightPos2);
		//glEnable(GL_LIGHT1);

		// Setup material
		glEnable(GL_COLOR_MATERIAL);
		glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

		// Set specular and shiniess
		float shininess = 15.0f;
		float specularColor[4] = {0.7, 0.7, 0.7, 1.f};
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess); // range 0 ~ 128
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specularColor);
	}


	// Hud Text
	m_HUD_text.push_back( "Keys" );
	m_HUD_text.push_back( "  W: Toggle WireFrame" );
	m_HUD_text.push_back( "  M: Change Render Method" );
	m_HUD_text.push_back( "  UP: Increase render faces" );
	m_HUD_text.push_back( "  DOWN: Decrease render faces" );
	m_HUD_text.push_back( "" );
	m_HUD_text.push_back( "" );
	m_HUD_text.push_back( "" );
	m_HUD_text.push_back( "Render method/faces" );
	m_HUD_text.push_back( "" );
	m_HUD_text.push_back( "" );
	
	// Read ply into mesh
	ReadPLYFile( "bunny.ply", m_mesh );
	m_mesh.GenerateNormals( true );		// Invert normals, since ply file's face are in clockwise order
	m_max_faces_to_render = m_mesh.GetNumFaces();

	return true;
}

void PlyViewer::UnInit()
{
	Scene::UnInit();
}

void PlyViewer::Update()
{
	Scene::Update();

	// Debug info
	{
		m_HUD_text.pop_back();
		m_HUD_text.pop_back();

		char str[1024];
		sprintf( str, "     %s", m_mesh.m_render_method == Mesh::RENDER_METHOD_IMMEDIATE ? "IMMEDIATE" : m_mesh.m_render_method == Mesh::RENDER_METHOD_VERTEX_ARRAYS ? "VERTEX ARRAYS" : "VERTEX BUFFER" );
		m_HUD_text.push_back( str );
		sprintf( str, "     %d", m_max_faces_to_render );
		m_HUD_text.push_back( str );
	}
}

void PlyViewer::Render( Renderer* renderer )
{
	// Clear back buffer
	glClearColor( BG_COLOR[0], BG_COLOR[1], BG_COLOR[2], BG_COLOR[3] );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) ;

	// Setup view matrix
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	m_camera.glLookAt();


	// Render mesh
	glPushMatrix();

	glColor4f( 0.5, 0.25, 0.0, 1 );

	glScalef( 10, 10, 10 );
	glEnable( GL_NORMALIZE );	// To normalize the normals

	m_mesh.Render(m_max_faces_to_render);

	glPopMatrix();


	// Axis
	DrawAxis();


	// HUD
	RenderHUD( renderer, 0.6, 0.85, m_HUD_text );
}
